Using the Playable Node
In game development, animations are crucial for bringing characters and scenes to life. Dora SSR engine provides a powerful animation handling node class—Playable. It serves as the base class for three animation systems:
- Model:
- A skeletal animation system implemented by the Dora SSR engine.
- Animation models are usually composed of a
.model
file, a.clip
file, and a.png
file.
- DragonBone:
- The open-source DragonBones animation system.
- Animation models typically consist of a file ending with
_ske.json
, a file ending with_tex.json
, and an image file ending with_tex.png
.
- Spine:
- The animation system of the well-known commercial software Spine2D.
- Animation models are generally composed of a
.json
(or.skel
) file, an.atlas
file, and a.png
file.
This tutorial will guide you on how to use the Playable node in your program, covering everything from loading animations to controlling playback.
1. Creating a Playable Instance
To use the Playable node, you first need to create an instance. Playable supports three animation systems, and the typical loading methods are as follows:
- Model files:
"model:"
prefix + the model file path without the suffix. - Spine files:
"spine:"
prefix + the Spine file path without the suffix. - DragonBones files:
"bone:"
prefix + the DragonBones file path without the suffix.
1.1 Example: Loading a Model Animation
- Lua
- Teal
- TypeScript
- YueScript
local Playable <const> = require("Playable")
-- Load Model animation
local modelPath = "model:assets/character"
local character = Playable(modelPath)
if character then
character.position = Vec2(100, 200)
stage:addChild(character)
else
print("Failed to load Model animation!")
end
local Playable <const> = require("Playable")
-- Load Model animation
local modelPath = "model:assets/character"
local character = Playable(modelPath)
if not character is nil then
character.position = Vec2(100, 200)
stage:addChild(character)
else
print("Failed to load Model animation!")
end
import { Playable, Vec2 } from "Dora";
// Load Model animation
const modelPath = "model:assets/character";
const character = Playable(modelPath);
if (character) {
character.position = Vec2(100, 200);
stage.addChild(character);
} else {
print("Failed to load Model animation!");
}
_ENV = Dora
-- Load Model animation
modelPath = "model:assets/character"
character = Playable modelPath
if character
with character
.position = Vec2 100, 200
\addTo stage
else
print "Failed to load Model animation!"
1.2 Example: Loading a Spine Animation
- Lua
- Teal
- TypeScript
- YueScript
local Playable <const> = require("Playable")
-- Load Spine animation
local spinePath = "spine:assets/monster"
local monster = Playable(spinePath)
if monster then
monster.position = Vec2(300, 200)
stage:addChild(monster)
else
print("Failed to load Spine animation!")
end
local Playable <const> = require("Playable")
-- Load Spine animation
local spinePath = "spine:assets/monster"
local monster = Playable(spinePath)
if not monster is nil then
monster.position = Vec2(300, 200)
stage:addChild(monster)
else
print("Failed to load Spine animation!")
end
import { Playable, Vec2 } from "Dora";
// Load Spine animation
const spinePath = "spine:assets/monster";
const monster = Playable(spinePath);
if (monster) {
monster.position = Vec2(300, 200);
stage.addChild(monster);
} else {
print("Failed to load Spine animation!");
}
_ENV = Dora
-- Load Spine animation
spinePath = "spine:assets/monster"
monster = Playable spinePath
if monster
with monster
.position = Vec2 300, 200
\addTo stage
else
print "Failed to load Spine animation!"
1.3 Example: Loading DragonBones Animation
- Lua
- Teal
- TypeScript
- YueScript
local Playable <const> = require("Playable")
-- Load DragonBones animation
local dragonBonePath = "bone:assets/dragon"
local dragon = Playable(dragonBonePath)
if dragon then
dragon.position = Vec2(500, 200)
stage:addChild(dragon)
else
print("Failed to load DragonBones animation!")
end
local Playable <const> = require("Playable")
-- Load DragonBones animation
local dragonBonePath = "bone:assets/dragon"
local dragon = Playable(dragonBonePath)
if not dragon is nil then
dragon.position = Vec2(500, 200)
stage:addChild(dragon)
else
print("Failed to load DragonBones animation!")
end
import { Playable, Vec2 } from "Dora";
// Load DragonBones animation
const dragonBonePath = "bone:assets/dragon";
const dragon = Playable(dragonBonePath);
if (dragon) {
dragon.position = Vec2(500, 200);
stage.addChild(dragon);
} else {
print("Failed to load DragonBones animation!");
}
_ENV = Dora
-- Load DragonBones animation
dragonBonePath = "bone:assets/dragon"
dragon = Playable dragonBonePath
if dragon
with dragon
.position = Vec2 500, 200
\addTo stage
else
print "Failed to load DragonBones animation!"
1.4 Example: Asynchronous Animation Loading
In real-world development, loading animations may take some time. You can use the Cache:loadAsync()
method to load animations asynchronously, executing a callback function upon completion.
- Lua
- Teal
- TypeScript
- YueScript
local Playable <const> = require("Playable")
local thread <const> = require("thread")
-- Asynchronously load Model animation
local modelPath = "model:assets/character"
thread(function()
if Cache:loadAsync(modelPath) then
local character = Playable(modelPath)
character.position = Vec2(100, 200)
stage:addChild(character)
else
print("Failed to load Model animation asynchronously!")
end
end)
local Playable <const> = require("Playable")
local thread <const> = require("thread")
-- Asynchronously load Model animation
local modelPath = "model:assets/character"
thread(function()
if Cache:loadAsync(modelPath) then
local character = Playable(modelPath)
if not character is nil then
character.position = Vec2(100, 200)
stage:addChild(character)
end
else
print("Failed to load Model animation asynchronously!")
end
end)
import { Playable, Vec2, Cache, thread } from "Dora";
// Asynchronously load Model animation
const modelPath = "model:assets/character";
thread(() => {
if (Cache.loadAsync(modelPath)) {
const character = Playable(modelPath);
if (character) {
character.position = Vec2(100, 200);
stage.addChild(character);
}
} else {
print("Failed to load Model animation asynchronously!");
}
});
_ENV = Dora
-- Asynchronously load Model animation
modelPath = "model:assets/character"
thread ->
if Cache\loadAsync modelPath
with Playable modelPath
.position = Vec2 100, 200
\addTo stage
else
print "Failed to load Model animation asynchronously!"
2. Playing Animations
Once you have created an instance, you can play a specific animation using the play
method.
- Lua
- Teal
- TypeScript
- YueScript
-- Play the "run" animation in a loop
local duration = character:play("run", true)
-- Play the "run" animation in a loop
local duration = character:play("run", true)
// Play the "run" animation ina loop
const duration = character.play("run", true);
-- Play the "run" animation in a loop
duration = character\play "run", true
- Parameters:
name
: The name of the animation to play.loop
(optional): Whether to loop the animation, default isfalse
.
- Return Value: The duration of the animation (in seconds).
3. Stopping Animations
Use the stop
method to stop the currently playing animation.
- Lua
- Teal
- TypeScript
- YueScript
-- Stop the animation
character:stop()
-- Stop the animation
character:stop()
// Stop the animation
character.stop();
-- Stop the animation
character\stop()
4. Setting Playback Speed
You can change the animation playback speed by adjusting the speed
property.
- Lua
- Teal
- TypeScript
- YueScript
-- Double the playback speed
character.speed = 2.0
-- Double the playback speed
character.speed = 2.0
// Double the playback speed
character.speed = 2.0;
-- Double the playback speed
character.speed = 2.0
- Note: The default value of
speed
is1.0
.
5. Flipping Animations
You can flip the animation horizontally by using the fliped
property, which is often used for character direction changes.
- Lua
- Teal
- TypeScript
- YueScript
-- Flip horizontally
character.fliped = true
-- Flip horizontally
character.fliped = true
// Flip horizontally
character.fliped = true;
-- Flip horizontally
character.fliped = true
fliped
:true
means flipped,false
means normal.
6. Getting Keypoint Coordinates
The getKey
method is used to get the coordinates of keypoints on the model, such as the character's hand or foot positions. In the Model animation system, keypoints are specific points set on the model. In DragonBone, keypoints are bone positions. In Spine2D, keypoints are vertex attachment positions.
- Lua
- Teal
- TypeScript
- YueScript
-- Get the coordinates of the character's right hand
local handPosition = character:getKey("right_hand")
print("Right hand coordinates:", handPosition.x, handPosition.y)
-- Get the coordinates of the character's right hand
local handPosition = character:getKey("right_hand")
print("Right hand coordinates:", handPosition.x, handPosition.y)
// Get the coordinates of the character's right hand
const handPosition = character.getKey("right_hand");
print("Right hand coordinates:", handPosition.x, handPosition.y);
-- Get the coordinates of the character's right hand
handPosition = character\getKey "right_hand"
print "Right hand coordinates:", handPosition.x, handPosition.y
- Parameters: The name of the keypoint (string).
- Return Value:
Vec2
, representing the coordinates of the keypoint.
7. Adding Child Nodes to Slots
The setSlot
method allows you to add child nodes to specific slots on the model, such as adding a weapon or equipment to a character.
- Lua
- Teal
- TypeScript
- YueScript
-- Create a sword sprite
local sword = Sprite("assets/sword.png")
-- Add the sword to the character's "right_hand" slot
character:setSlot("right_hand", sword)
-- Create a sword sprite
local sword = Sprite("assets/sword.png")
-- Add the sword to the character's "right_hand" slot
character:setSlot("right_hand", sword)
// Create a sword sprite
const sword = Sprite("assets/sword.png");
// Add the sword to the character's "right_hand" slot
character.setSlot("right_hand", sword);
-- Create a sword sprite
sword = Sprite "assets/sword.png"
-- Add the sword to the character's "right_hand" slot
character\setSlot "right_hand", sword
- Parameters:
name
: The name of the slot.item
: The node object to be added.
8. Getting Child Nodes from Slots
You can retrieve the child node from a specific slot using the getSlot
method.
- Lua
- Teal
- TypeScript
- YueScript
-- Get the node in the "right_hand" slot
local equippedItem = character:getSlot("right_hand")
if equippedItem then
print("Equipped item:", equippedItem)
else
print("Slot is empty")
end
-- Get the node in the "right_hand" slot
local equippedItem = character:getSlot("right_hand")
if not equippedItem is nil then
print("Equipped item:", equippedItem)
else
print("Slot is empty")
end
// Get the node in the "right_hand" slot
const equippedItem = character.getSlot("right_hand");
if (equippedItem) {
print("Equipped item:", equippedItem);
} else {
print("Slot is empty");
}
-- Get the node in the "right_hand" slot
equippedItem = character\getSlot "right_hand"
if equippedItem
print "Equipped item:", equippedItem
else
print "Slot is empty"
- Return Value: A
Node
object ornil
.
9. Listening for Animation End Events
You can register a callback function using the onAnimationEnd
method, which triggers when the animation playback ends.
- Lua
- Teal
- TypeScript
- YueScript
-- Register an animation end callback
character:onAnimationEnd(function(animationName, target)
print("Animation ended:", animationName)
-- Perform subsequent actions here, such as switching animations
end)
-- Register an animation end callback
character:onAnimationEnd(function(animationName: string, target: Playable.Type)
print("Animation ended:", animationName)
-- Perform subsequent actions here, such as switching animations
end)
// Register an animation end callback
character.onAnimationEnd((animationName, target) => {
print("Animation ended:", animationName);
// Perform subsequent actions here, such as switching animations
});
-- Register an animation end callback
character\onAnimationEnd (animationName, target) ->
print "Animation ended:", animationName
-- Perform subsequent actions here, such as switching animations
- Parameters:
callback
: A callback function that accepts two parameters:animationName
andtarget
.
10. Comprehensive Example
The following is a comprehensive example that demonstrates how to create a character, play animations, equip items, and handle animation end events.
- Lua
- Teal
- TypeScript
- YueScript
local Playable <const> = require("Playable")
local Sprite <const> = require("Sprite")
local Vec2 <const> = require("Vec2")
local sleep <const> = require("sleep")
-- Create a character
local character = Playable("model:assets/hero.model")
character.position = Vec2(200, 300)
-- Set properties
character.speed = 1.0
character.fliped = false
-- Play idle animation
character:play("idle", true)
-- Create a sword and equip it
local sword = Sprite("assets/sword.png")
character:setSlot("right_hand", sword)
-- Register an animation end event
character:onAnimationEnd(function(animationName, target)
if animationName == "attack" then
-- Return to idle state after attack ends
target:play("idle", true)
end
end)
-- Perform an attack every 3 seconds
character:loop(function()
-- Play attack animation
character:play("attack")
sleep(3)
end)
local Playable <const> = require("Playable")
local Sprite <const> = require("Sprite")
local Vec2 <const> = require("Vec2")
local sleep <const> = require("sleep")
-- Create a character
local character = Playable("model:assets/hero.model")
if not character is nil then
character.position = Vec2(200, 300)
-- Set properties
character.speed = 1.0
character.fliped = false
-- Play idle animation
character:play("idle", true)
-- Create a sword and equip it
local sword = Sprite("assets/sword.png")
character:setSlot("right_hand", sword)
-- Register an animation end event
character:onAnimationEnd(function(animationName: string, target: Playable.Type)
if animationName == "attack" then
-- Return to idle state after attack ends
target:play("idle", true)
end
end)
-- Perform an attack every 3 seconds
character:loop(function(): boolean
-- Play attack animation
character:play("attack")
sleep(3)
return false
end)
end
import { Playable, Sprite, Vec2, sleep } from "Dora";
// Create a character
const character = Playable("model:assets/hero.model");
if (character) {
character.position = Vec2(200, 300);
// Set properties
character.speed = 1.0;
character.fliped = false;
// Play idle animation
character.play("idle", true);
// Create a sword and equip it
const sword = Sprite("assets/sword.png");
character.setSlot("right_hand", sword);
// Register an animation end event
character.onAnimationEnd((animationName, target) => {
if (animationName === "attack") {
// Return to idle state after attack ends
target.play("idle", true);
}
});
// Perform an attack every 3 seconds
character.loop(() => {
// Play attack animation
character.play("attack");
sleep(3);
return false;
});
}
_ENV = Dora
-- Create a character
with Playable "model:assets/hero.model"
.position = Vec2 200, 300
-- Set properties
.speed = 1.0
.fliped = false
-- Play idle animation
\play "idle", true
-- Create a sword and equip it
sword = Sprite "assets/sword.png"
\setSlot "right_hand", sword
-- Register an animation end event
\onAnimationEnd (animationName, target) ->
if animationName == "attack"
-- Return to idle state after attack ends
target\play "idle", true
-- Perform an attack every 3 seconds
\loop ->
-- Play attack animation, no looping
\play "attack"
sleep 3
11. Conclusion
Through this tutorial, you have learned how to use the Playable node class in Dora SSR to load and control various animation models. Playable offers a rich set of interfaces, supporting multiple animation systems, making it easy to add complex animation effects to your game.
Since Spine2D is commercial software, using its animations requires adhering to the corresponding license agreement. Please refer to the official Spine website for more details.
We hope this tutorial helps you in your game development journey, and we wish you success!